﻿// Deep Earth is a community project available under the Microsoft Public License (Ms-PL)
// Code is provided as is and with no warrenty – Use at your own risk
// View the project and the latest code at http://DeepEarth.codeplex.com/

using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ServiceModel;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
using DeepEarth.BingMapsToolkit.Client.Common;
using DeepEarth.BingMapsToolkit.Client.Controls.GeocodeService;
using Microsoft.Maps.MapControl;
using Microsoft.Maps.MapControl.Core;

namespace DeepEarth.BingMapsToolkit.Client.Controls
{
    [TemplatePart(Name = PART_ClearFind, Type = typeof (Button))]
    [TemplatePart(Name = PART_FindSearchbox, Type = typeof (TextBox))]
    [TemplatePart(Name = PART_TriggerFind, Type = typeof (Button))]
    [TemplatePart(Name = PART_SearchResults, Type = typeof (ListBox))]
    [TemplatePart(Name = PART_LoadingIndicator, Type = typeof (UIElement))]
    [TemplateVisualState(Name = VSM_Normal, GroupName = VSM_CommonStates)]
    [TemplateVisualState(Name = VSM_MouseOver, GroupName = VSM_CommonStates)]
    public class FindControl : Control, IMapControl<MapCore>
    {
        private const string PART_ClearFind = "PART_ClearFind";
        private const string PART_FindSearchbox = "PART_FindSearchbox";
        private const string PART_LoadingIndicator = "PART_LoadingIndicator";
        private const string PART_SearchResults = "PART_SearchResults";
        private const string PART_TriggerFind = "PART_TriggerFind";
        private const string VSM_CommonStates = "CommonStates";
        private const string VSM_MouseOver = "MouseOver";
        private const string VSM_Normal = "Normal";

        private Button clearFind;
        private TextBox findSearchbox;
        private GeocodeServiceClient geocodeClient;
        private GeocodeLayer geocodeLayer;
        private int geocodesInProgress;

        private bool isMouseOver;
        private UIElement loadingIndicator;
        private string mapName;
        private ListBox searchResults;
        private Button triggerFind;

        public FindControl()
        {
            DefaultStyleKey = typeof (FindControl);
            Loaded += FindControl_Loaded;
        }

        public LocationRect PreferredTargetView { get; set; }

        private GeocodeServiceClient GeocodeClient
        {
            get
            {
                if (null == geocodeClient)
                {
                    //Handle http/https; OutOfBrowser is currently supported on the MapControl only for http pages
                    bool httpsUriScheme = !Application.Current.IsRunningOutOfBrowser &&
                                          HtmlPage.Document.DocumentUri.Scheme.Equals(Uri.UriSchemeHttps);
                    BasicHttpBinding binding = httpsUriScheme
                                                   ? new BasicHttpBinding(BasicHttpSecurityMode.Transport)
                                                   : new BasicHttpBinding(BasicHttpSecurityMode.None);
                    var serviceUri =
                        new UriBuilder("http://dev.virtualearth.net/webservices/v1/GeocodeService/GeocodeService.svc");
                    if (httpsUriScheme)
                    {
                        //For https, change the UriSceheme to https and change it to use the default https port.
                        serviceUri.Scheme = Uri.UriSchemeHttps;
                        serviceUri.Port = -1;
                    }

                    //Create the Service Client
                    geocodeClient = new GeocodeServiceClient(binding, new EndpointAddress(serviceUri.Uri));
                    geocodeClient.GeocodeCompleted += geocodeService_GeocodeCompleted;
                }
                return geocodeClient;
            }
        }

        private GeocodeLayer GeocodeLayer
        {
            get
            {
                if (null == geocodeLayer)
                {
                    geocodeLayer = new GeocodeLayer(MapInstance);
                }
                return geocodeLayer;
            }
        }

        #region IMapControl<MapCore> Members

        public string MapName
        {
            get { return mapName; }
            set
            {
                mapName = value;
                setMapInstance(MapName);
            }
        }

        public MapCore MapInstance { get; set; }

        public void Dispose()
        {
            MouseEnter -= mouseEnter;
            MouseLeave -= mouseLeave;
            MapInstance = null;
        }

        #endregion

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            clearFind = (Button) GetTemplateChild(PART_ClearFind);
            findSearchbox = (TextBox) GetTemplateChild(PART_FindSearchbox);
            triggerFind = (Button) GetTemplateChild(PART_TriggerFind);
            searchResults = (ListBox) GetTemplateChild(PART_SearchResults);
            loadingIndicator = (UIElement) GetTemplateChild(PART_LoadingIndicator);

            if (clearFind != null)
            {
                clearFind.Click += clearFind_Click;
            }
            if (triggerFind != null)
            {
                triggerFind.Click += triggerFind_Click;
            }
            if (findSearchbox != null)
            {
                findSearchbox.KeyDown += findSearchbox_KeyDown;
            }
            if (searchResults != null)
            {
                searchResults.SelectionChanged += searchResults_SelectionChanged;
            }
        }

        private void findSearchbox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                triggerSearch();
            }
        }

        private void searchResults_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                //trigger map view
                var item = (GeocodeResult) e.AddedItems[0];
                if (item.Locations != null)
                {
                    Location loc = GeocodeLayer.AddResult(item);
                    MapInstance.SetView(loc, 18);
                }
            }
        }

        private void triggerFind_Click(object sender, RoutedEventArgs e)
        {
            triggerSearch();
        }

        private void clearFind_Click(object sender, RoutedEventArgs e)
        {
            clear();
        }

        private void clear()
        {
            //clear everything
            GeocodeLayer.Clear();
            if (searchResults.ItemsSource != null)
            {
                searchResults.ItemsSource = null;  
            }else
            {
                searchResults.Items.Clear();               
            }
            searchResults.Visibility = Visibility.Collapsed;
            findSearchbox.Text = "";
        }

        private void triggerSearch()
        {
            if (findSearchbox != null && findSearchbox.Text.Length > 0)
            {
                searchResults.ItemsSource = null;
                searchResults.Visibility = Visibility.Collapsed;
                string searchText = findSearchbox.Text.Trim();                    

                geocodeAddress(searchText);
            }
        }

        private void FindControl_Loaded(object sender, RoutedEventArgs e)
        {
            setMapInstance(MapName);

            MouseEnter += mouseEnter;
            MouseLeave += mouseLeave;
            GoToState(true);
        }

        private void setMapInstance(string mapname)
        {
            MapInstance = Utilities.FindVisualChildByName<MapCore>(Application.Current.RootVisual, mapname);
        }

        private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
        {
            geocodesInProgress--;
            GoToState(true);
            try
            {
                if (e.Result.ResponseSummary.StatusCode == ResponseStatusCode.Success && e.Result.Results.Count > 0)
                {
                    searchResults.ItemsSource = e.Result.Results;
                    searchResults.Visibility = Visibility.Visible;
                }
                else
                {
                    searchResults.Items.Add(new GeocodeResult { DisplayName = "Address Not Found" });
                    searchResults.Visibility = Visibility.Visible;
                }
            }
            catch(Exception ex)
            {
                //swollow error?
                Debug.WriteLine(ex.Message);
            }
        }

        private void geocodeAddress(string address)
        {
            var request = new GeocodeRequest
                              {
                                  Culture = MapInstance.Culture,
                                  Query = address,
                                  UserProfile = new UserProfile { MapView = PreferredTargetView ?? MapInstance.BoundingRectangle },
                                  ExecutionOptions = new ExecutionOptions
                                                         {
                                                             // Don't raise exceptions.
                                                             SuppressFaults = true
                                                         },
                                  Options = new GeocodeOptions {Filters = new ObservableCollection<FilterBase>()}
                              };

            // accept results with low confidence as we show these back to the user to choose from.
            var filter = new ConfidenceFilter {MinimumConfidence = Confidence.Low};
            request.Options.Filters.Add(filter);

            geocodesInProgress++;
            GoToState(true);

            MapInstance.CredentialsProvider.GetCredentials(
                credentials =>
                    {
                        //Pass in credentials for web services call.
                        request.Credentials = credentials;

                        // Make asynchronous call to fetch the data ... pass state object.
                        GeocodeClient.GeocodeAsync(request, address);
                    });
        }

        private void mouseLeave(object sender, MouseEventArgs e)
        {
            isMouseOver = false;
            if (IsEnabled)
            {
                GoToState(true);
            }
        }

        private void mouseEnter(object sender, MouseEventArgs e)
        {
            isMouseOver = true;
            if (IsEnabled)
            {
                GoToState(true);
            }
        }

        private void GoToState(bool useTransitions)
        {
            if (isMouseOver)
            {
                VisualStateManager.GoToState(this, VSM_MouseOver, useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, VSM_Normal, useTransitions);
            }
            if (loadingIndicator != null)
            {
                loadingIndicator.Visibility = geocodesInProgress > 0 ? Visibility.Visible : Visibility.Collapsed;
            }
        }
    }
}